package location

import (
	"crypto/md5"
	"encoding/xml"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"

	"github.com/alecthomas/log4go"
	"pac/conf"
)

type amapLbsLocateResp struct {
	Status       string `xml:"status"` // 返回状态。0/1。0 代表 false
	InfoCode     string `xml:"infocode,omitempty"`
	Info         string `xml:"info,omitempty"`
	Key          string `xml:"key,omitempty"`
	SecCode      string `xml:"sec_code,omitempty"`
	SecCodeDebug string `xml:"sec_code_debug,omitempty"`
	Result       struct {
		Type      string  `xml:"type"` // 定位类型。0: 没有得到结果
		Imei      string  `xml:"imei,omitempty"`
		Location  string  `xml:"location,omitempty"` // 经纬度信息 （经度,纬度）
		Radius    string  `xml:"radius,omitempty"`
		Desc      string  `xml:"desc,omitempty"`
		Country   string  `xml:"country,omitempty"`
		Province  string  `xml:"province,omitempty"`
		CityCode  string  `xml:"citycode,omitempty"`
		City      string  `xml:"city,omitempty"`
		AdCode    string  `xml:"adcode,omitempty"` // 行政区域编码
		Road      string  `xml:"road,omitempty"`
		Street    string  `xml:"street,omitempty"`
		Poi       string  `xml:"poi,omitempty"`
		Longitude float64 // 解码后的经度
		Latitude  float64 // 解码后的纬度
		Accuracy  float64 // 解码后的定位精度
	} `xml:"result,omitempty"`
}

func amapLbsLocate(imei string, stations *Station) (ErrCode, *amapLbsLocateResp) {
	if stations == nil {
		log4go.Warn("stations is nil")
		return ErrInvalidParam, nil
	}
	if len(stations.Cells) == 0 && len(stations.Wifis) == 0 {
		log4go.Warn("stations not contain any station")
		return ErrInvalidParam, nil
	}
	aMapLBSKey := conf.AMapKeys.Lbs
	if len(aMapLBSKey) == 0 {
		log4go.Trace("AMapLBSKey is empty")
		return ErrFailure, nil
	}

	cells := stations.Cells
	wifis := stations.Wifis

	// 准备 RUL 参数

	lbsURL := "http://apilocate.amap.com/position"

	var isCell bool

	lbsURL += "?accesstype="
	if len(cells) != 0 && (stations.NetConnType == NetConnTypeCell || len(wifis) == 0) {
		lbsURL += "0"
		isCell = true
	} else {
		lbsURL += "1"
	}

	if len(imei) > 0 {
		lbsURL += "&imei=" + imei
	}

	if len(stations.Imsi) > 0 {
		lbsURL += "&imsi=" + stations.Imsi
	}

	if len(cells) != 0 {
		if stations.CellRadioType >= GSM && stations.CellRadioType <= WCDMA {
			lbsURL += "&network=" + string(stations.CellRadioType)
		}

		lbsURL += "&cdma="
		if stations.CellRadioType == CDMA {
			lbsURL += "1"
		} else {
			lbsURL += "0"
		}
	}

	if isCell {
		var bts string

		cell := cells[0]
		if stations.CellRadioType == CDMA {
			bts = fmt.Sprintf("%d,%d,%d,,,%d", cell.MNC, cell.LAC, cell.CID, cell.Rssi)
		} else {
			signal := NormalizationCellRssi(cell.Rssi)
			bts = fmt.Sprintf("%d,%d,%d,%d,%d", cell.MCC, cell.MNC, cell.LAC, cell.CID, signal)
		}

		lbsURL += "&bts=" + bts
		cells = cells[1:]
		lbsURL += amapLbsMakeNearbts(cells, stations.CellRadioType)
		cells = nil
	} else if len(wifis) != 0 {
		lbsURL += "&mmac=" + fmt.Sprintf("%s,%d,%s", wifis[0].MacAddr, wifis[0].Rssi, url.PathEscape(wifis[0].SSID))

		wifis = wifis[1:]
		lbsURL += amapLbsMakeMacs(wifis)
		wifis = nil
	}

	lbsURL += amapLbsMakeNearbts(cells, stations.CellRadioType)
	lbsURL += amapLbsMakeMacs(wifis)

	lbsURL += "&output=xml"
	lbsURL += "&key=" + aMapLBSKey

	log4go.Finest("Locate URL: %v", lbsURL)

	// HTTP 请求

	resp, err := http.Get(lbsURL)
	if err != nil {
		log4go.Warn("http.Get: %v", err)
		return ErrFailure, nil
	}

	defer resp.Body.Close()

	// 解析 HTTP 回响

	var locateResp amapLbsLocateResp
	err = xml.NewDecoder(resp.Body).Decode(&locateResp)
	if err != nil {
		log4go.Warn("LBS failure: the http response seems not xml: %v", err)
		return ErrFailure, nil
	}

	log4go.Finest("Locate response: %v", locateResp)

	if locateResp.Status == "0" {
		log4go.Warn("LBS failure: the http response false: %v", locateResp)
		return ErrFailure, nil
	}
	if locateResp.Result.Type == "0" {
		log4go.Warn("LBS failure: the http response cannot get locate result: %v", locateResp)
		return ErrFailure, nil
	}

	latLng := strings.Split(locateResp.Result.Location, ",")
	if len(latLng) != 2 {
		log4go.Warn("LBS failure: location field of http response seems not latitude and longitude %v", latLng)
		return ErrFailure, nil
	}

	locateResp.Result.Longitude, err = strconv.ParseFloat(latLng[0], 64)
	if err != nil {
		log4go.Warn("LBS failure: location field of http response seems not longitude %v", latLng[0])
		return ErrFailure, nil
	}
	locateResp.Result.Latitude, err = strconv.ParseFloat(latLng[1], 64)
	if err != nil {
		log4go.Warn("LBS failure: location field of http response seems not latitude %v", latLng[1])
		return ErrFailure, nil
	}
	if len(locateResp.Result.Radius) > 1 {
		locateResp.Result.Accuracy, err = strconv.ParseFloat(locateResp.Result.Radius, 64)
		if err != nil {
			log4go.Warn("LBS failure: location field of http response seems not radius %v", locateResp.Result.Radius)
			return ErrFailure, nil
		}
	}

	return ErrSuccess, &locateResp
}

func amapLbsMakeNearbts(cells []*Cell, cellRadioType CellRadioType) string {
	if len(cells) == 0 {
		return ""
	}
	nearbts := "&nearbts="
	sep := ""
	for _, cell := range cells {
		if cellRadioType == CDMA {
			nearbts += sep + fmt.Sprintf("%d,%d,%d,,,%d", cell.MNC, cell.LAC, cell.CID, cell.Rssi)
		} else {
			signal := NormalizationCellRssi(cell.Rssi)
			nearbts += sep + fmt.Sprintf("%d,%d,%d,%d,%d", cell.MCC, cell.MNC, cell.LAC, cell.CID, signal)
		}
		sep = "|"
	}
	return nearbts
}

func amapLbsMakeMacs(wifis []*Wifi) string {
	if len(wifis) == 0 {
		return ""
	}
	macs := "&macs="
	sep := ""
	for _, wifi := range wifis {
		macs += sep + fmt.Sprintf("%s,%d,%s", wifi.MacAddr, wifi.Rssi, url.PathEscape(wifi.SSID))
		sep = "|"
	}
	return macs
}

type amapLbIPLocateResp struct {
	Status    string `xml:"status"`              // 值为0或1；0表示失败；1表示成功
	Info      string `xml:"info,omitempty"`      // 返回状态说明，status为0时，info返回错误原因，否则返回“OK”。
	InfoCode  string `xml:"infocode,omitempty"`  // 返回状态说明,10000代表正确,详情参阅info状态表
	Province  string `xml:"province,omitempty"`  // 若为直辖市则显示直辖市名称；如果在局域网 IP网段内，则返回“局域网”；非法IP以及国外IP则返回空
	City      string `xml:"city,omitempty"`      // 若为直辖市则显示直辖市名称；如果为局域网网段内IP或者非法IP或国外IP，则返回空
	AdCode    string `xml:"adcode,omitempty"`    // 行政区域编码
	Rectangle string `xml:"rectangle,omitempty"` // 所在城市矩形区域范围。所在城市范围的左下右上对标对
}

func amapLbIP(ip string) (ErrCode, *amapLbIPLocateResp) {
	netIP := net.ParseIP(ip)
	if netIP == nil {
		log4go.Warn("invalid ip")
		return ErrInvalidParam, nil
	}
	aMapLBIPKey := conf.AMapKeys.WebService
	if len(aMapLBIPKey) == 0 {
		log4go.Trace("aMapLBIPKey is empty")
		return ErrFailure, nil
	}

	// 准备 RUL 参数

	lbsURL := "https://restapi.amap.com/v3/ip"

	lbsURL += "?ip="

	lbsURL += url.PathEscape(ip)

	lbsURL += "&output=xml"
	lbsURL += "&key=" + aMapLBIPKey

	log4go.Finest("Locate URL: %v", lbsURL)

	// HTTP 请求

	resp, err := http.Get(lbsURL)
	if err != nil {
		log4go.Warn("http.Get: %v", err)
		return ErrFailure, nil
	}

	defer resp.Body.Close()

	// 解析 HTTP 回响

	var locateResp amapLbIPLocateResp
	err = xml.NewDecoder(resp.Body).Decode(&locateResp)
	if err != nil {
		log4go.Warn("LBIP failure: the http response seems not xml: %v", err)
		return ErrFailure, nil
	}

	log4go.Finest("Locate response: %v", locateResp)

	if locateResp.Status == "0" {
		log4go.Warn("LBIP failure: the http response false: %v", locateResp)
		return ErrFailure, nil
	}

	return ErrSuccess, &locateResp
}

// 逆地理编码

// 商圈信息
type amapBusinessArea struct {
	Location string `xml:"location,omitempty"` // 商圈中心点经纬度
	Name     string `xml:"name,omitempty"`     //  商圈名称
	ID       string `xml:"id,omitempty"`       // 商圈所在区域的 adcode 例如：朝阳区/海淀区
}

// 道路信息
type amapRoad struct {
	ID        string `xml:"id,omitempty"`        // 道路id
	Name      string `xml:"name,omitempty"`      // 道路名称
	Distance  string `xml:"distance,omitempty"`  // 道路到请求坐标的距离
	Direction string `xml:"direction,omitempty"` // 方位 输入点和此路的相对方位
	Location  string `xml:"location,omitempty"`  // 坐标点
}

// 道路交叉口
type amapRoadinter struct {
	Distance   string `xml:"distance,omitempty"`    // 交叉路口到请求坐标的距离 单位：米
	Direction  string `xml:"direction,omitempty"`   // 方位 输入点相对路口的方位
	Location   string `xml:"location,omitempty"`    // 路口经纬度
	FirstID    string `xml:"first_id,omitempty"`    // 第一条道路id
	FirstName  string `xml:"first_name,omitempty"`  // 第一条道路名称
	SecondID   string `xml:"second_id,omitempty"`   // 第二条道路id
	SecondName string `xml:"second_name,omitempty"` // 第二条道路名称
}

// poi信息
type amapPoi struct {
	ID           string `xml:"id,omitempty"`           // poi的id
	Name         string `xml:"name,omitempty"`         // poi点名称
	Type         string `xml:"type,omitempty"`         // poi类型
	Tel          string `xml:"tel,omitempty"`          // 电话
	Direction    string `xml:"direction,omitempty"`    // 方向 为输入点相对建筑物的方位
	Distance     string `xml:"distance,omitempty"`     // 该POI到请求坐标的距离 单位：米
	Location     string `xml:"location,omitempty"`     // 坐标点
	Address      string `xml:"address,omitempty"`      // poi地址信息
	PoiWeight    string `xml:"poiweight,omitempty"`    //
	BusinessArea string `xml:"businessarea,omitempty"` // poi所在商圈名称
}

// aoi信息
type amapAoi struct {
	ID       string `xml:"id,omitempty"`       // 所属 aoi的id
	Name     string `xml:"name,omitempty"`     // 所属 aoi 名称
	AdCode   string `xml:"adcode,omitempty"`   // 所属 aoi 所在区域编码
	Location string `xml:"location,omitempty"` // 所属 aoi 中心点坐标
	Area     string `xml:"area,omitempty"`     // 所属aoi点面积 单位：平方米
}

// 逆地理编码回响
type amapRegeocodeResp struct {
	//XMLName  xml.Name `xml:"response"`
	Status    string `xml:"status"` // 返回结果状态值: 返回值为 0 或 1，0 表示请求失败；1 表示请求成功。
	InfoCode  string `xml:"infocode,omitempty"`
	Info      string `xml:"info,omitempty"` // 返回状态说明: 当 status 为 0 时，info 会返回具体错误原因，否则返回“OK”
	Regeocode struct {
		FormattedAddr    string `xml:"formatted_address,omitempty"` // 结构化地址信息 省份＋城市＋区县＋城镇＋乡村＋街道＋门牌号码、省份＋城市＋区县＋海域信息
		AddressComponent struct {
			// 地址元素列表
			Country      string `xml:"country,omitempty"`  // 国家
			Province     string `xml:"province,omitempty"` // 重庆市
			City         string `xml:"city,omitempty"`     // 坐标点所在城市名称
			Citycode     string `xml:"citycode,omitempty"` // 城市编码
			District     string `xml:"district,omitempty"` // 坐标点所在区
			AdCode       string `xml:"adcode,omitempty"`   // 行政区编码
			Township     string `xml:"township,omitempty"` // 坐标点所在乡镇/街道（此街道为社区街道，不是道路信息）
			TownCode     string `xml:"towncode,omitempty"` // 乡镇街道编码
			Neighborhood struct {
				// 社区信息列表
				Name string `xml:"name,omitempty"` // 社区名称 例如：北京大学
				Type string `xml:"type,omitempty"` // POI类型 例如：科教文化服务;学校;高等院校
			} `xml:"neighborhood,omitempty"`
			Building struct {
				// 楼信息列表
				Name string `xml:"name,omitempty"` // 建筑名称 例如：北京大学
				Type string `xml:"type,omitempty"` // 类型 例如：科教文化服务;学校;高等院校
			} `xml:"building,omitempty"`
			StreetNumber struct {
				// 门牌信息列表
				Street    string `xml:"street,omitempty"`    // 街道名称 例如：中关村北二条
				Number    string `xml:"number,omitempty"`    // 门牌号 例如：3号
				Location  string `xml:"location,omitempty"`  // 坐标点 经纬度坐标点：经度，纬度
				Direction string `xml:"direction,omitempty"` // 方向 坐标点所处街道方位
				Distance  string `xml:"distance,omitempty"`  // 门牌地址到请求坐标的距离 单位：米
			} `xml:"streetNumber,omitempty"`
			SeaArea          string `xml:"seaArea,omitempty"` // 所属海域信息 例如：渤海
			BusinessAreaList struct {
				// 经纬度所属商圈列表
				Type          string `xml:"type,attr,omitempty"`
				BusinessAreas []amapBusinessArea
			} `xml:"businessAreas,omitempty"`
		} `xml:"addressComponent,omitempty"`
		PoiList struct {
			// poi信息列表
			Type string    `xml:"type,attr,omitempty"`
			Pois []amapPoi `xml:"poi,omitempty"`
		} `xml:"pois,omitempty"`
		RoadList struct {
			// 道路信息列表
			Type  string     `xml:"type,attr,omitempty"`
			Roads []amapRoad `xml:"road,omitempty"`
		} `xml:"roads,omitempty"`
		RoadinterList struct {
			// 道路交叉口列表
			Type       string          `xml:"type,attr,omitempty"`
			Roadinters []amapRoadinter `xml:"roadinter,omitempty"`
		} `xml:"roadinters,omitempty"`
		AoiList struct {
			// aoi信息列表
			Type string    `xml:"type,attr,omitempty"`
			Aois []amapAoi `xml:"aoi,omitempty"`
		} `xml:"aois,omitempty"`
	} `xml:"regeocode,omitempty"`
}

func amapRegeocode(latLon LatLonItf, accuracy float64) (ErrCode, *amapRegeocodeResp) {
	if latLon == nil {
		log4go.Warn("latLon is nil")
		return ErrInvalidParam, nil
	}
	aMapWebServiceKey := conf.AMapKeys.WebService
	if len(aMapWebServiceKey) == 0 {
		log4go.Trace("AMapWebServiceKey is empty")
		return ErrFailure, nil
	}

	paramMap := map[string]string{}

	regeoURL := "http://restapi.amap.com/v3/geocode/regeo"

	paramMap["location"] = fmt.Sprintf("%.6f,%.6f", latLon.GetLon(), latLon.GetLat())
	paramMap["poitype"] = ""
	if accuracy < 1000 {
		paramMap["radius"] = "1000"
	} else {
		a := int(accuracy + 0.5)
		paramMap["radius"] = strconv.Itoa(a)
	}
	paramMap["extensions"] = "all"
	paramMap["batch"] = "false"
	paramMap["output"] = "xml"
	paramMap["key"] = aMapWebServiceKey

	urlParam := ""
	var keys []string
	for k := range paramMap {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	sep := ""
	for _, k := range keys {
		urlParam += sep + k + "=" + paramMap[k]
		sep = "&"
	}

	// 接口签名。参考 http://lbs.amap.com/faq/account/key/72
	aMapWebServicePrivate := conf.AMapKeys.WebServicePrivate
	if len(aMapWebServicePrivate) != 0 {
		urlParamPre := urlParam + aMapWebServicePrivate
		d := md5.New()
		d.Write([]byte(urlParamPre))
		urlParam += "sig=" + fmt.Sprintf("%x", d.Sum(nil))
	}

	// 完成 URL

	regeoURL += "?" + urlParam

	log4go.Finest("Regeocode URL: %v", regeoURL)

	// HTTP 请求

	resp, err := http.Get(regeoURL)
	if err != nil {
		log4go.Warn(err)
		return ErrFailure, nil
	}

	defer resp.Body.Close()

	// 解析 HTTP 回响

	var geocodeResp amapRegeocodeResp
	err = xml.NewDecoder(resp.Body).Decode(&geocodeResp)
	if err != nil {
		log4go.Warn("Regeocode failure: the http response seems not xml: %v", err)
		return ErrFailure, nil
	}

	log4go.Finest("Regeocode response: %v", geocodeResp)

	if geocodeResp.Status == "0" {
		log4go.Warn("Regeocode failure: the http response false: %v", geocodeResp)
		return ErrFailure, nil
	}

	return ErrSuccess, &geocodeResp
}

type amapLive struct {
	Province      string `xml:"province,omitempty"`      // 省份名
	City          string `xml:"city,omitempty"`          // 城市名
	AdCode        string `xml:"adcode,omitempty"`        // 区域编码
	Weather       string `xml:"weather,omitempty"`       // 天气现象，天气现象对应描述
	Temperature   string `xml:"temperature,omitempty"`   // 实时气温，单位：摄氏度
	WindDirection string `xml:"winddirection,omitempty"` // 风向，风向编码对应描述
	WindPower     string `xml:"windpower,omitempty"`     // 风力，此处返回的是风力编码，风力编码对应风力级别，单位：级
	Humidity      string `xml:"humidity,omitempty"`      // 空气湿度
	ReportTime    string `xml:"reporttime,omitempty"`    // 数据发布的时间
}
type amapCast struct {
	Date         string `xml:"date,omitempty"`         // 日期
	Week         string `xml:"week,omitempty"`         // 星期几
	DayWeather   string `xml:"dayweather,omitempty"`   // 白天天气现象
	NightWeather string `xml:"nightweather,omitempty"` // 晚上天气现象
	DayTemp      string `xml:"daytemp,omitempty"`      // 白天温度
	NightTemp    string `xml:"nighttemp,omitempty"`    // 晚上温度
	DayWind      string `xml:"daywind,omitempty"`      // 白天风向
	NightWind    string `xml:"nightwind,omitempty"`    // 晚上风向
	DayPower     string `xml:"daypower,omitempty"`     // 白天风力
	NightPower   string `xml:"nightpower,omitempty"`   // 晚上风力
}
type amapForecast struct {
	City       string `xml:"city,omitempty"`       // 城市名
	AdCode     string `xml:"adcode,omitempty"`     // 城市编码
	Province   string `xml:"province,omitempty"`   // 省份名称
	ReportTime string `xml:"reporttime,omitempty"` // 数据发布的时间
	CastList   struct {
		// 预报数据list结构，元素cast,按顺序为当天、第二天、第三天的预报数据
		Type  string     `xml:"type,attr,omitempty"`
		Casts []amapCast `xml:"cast,omitempty"`
	} `xml:"casts,omitempty"`
}

// 天气查询回响
type amapWeatherResp struct {
	//XMLName  xml.Name `xml:"response"`
	Status   string `xml:"status"`             // 返回结果状态值: 返回值为 0 或 1，0 表示请求失败；1 表示请求成功。
	Count    string `xml:"count"`              // 返回结果总数目
	Info     string `xml:"info,omitempty"`     // 返回状态说明: 当 status 为 0 时，info 会返回具体错误原因，否则返回“OK”
	InfoCode string `xml:"infocode,omitempty"` // 返回状态说明,1000代表正确
	LiveList struct {
		// 实况天气数据信息
		Type  string     `xml:"type,attr,omitempty"`
		Lives []amapLive `xml:"live,omitempty"`
	} `xml:"lives,omitempty"`
	ForecastList struct {
		// 预报天气信息数据
		Type      string         `xml:"type,attr,omitempty"`
		Forecasts []amapForecast `xml:"forecast,omitempty"`
	} `xml:"forecasts,omitempty"`
}

func amapQueryWeather(addrAdCode string, extensions bool) (ErrCode, *amapWeatherResp) {
	if len(addrAdCode) == 0 {
		log4go.Warn("addrAdCode is empty")
		return ErrInvalidParam, nil
	}
	aMapWebServiceKey := conf.AMapKeys.WebServicePrivate
	if len(aMapWebServiceKey) == 0 {
		log4go.Trace("AMapWebServiceKey is empty")
		return ErrFailure, nil
	}

	paramMap := map[string]string{}

	weatherURL := "http://restapi.amap.com/v3/weather/weatherInfo"
	paramMap["city"] = addrAdCode
	if extensions {
		paramMap["extensions"] = "all"
	} else {
		paramMap["extensions"] = "base"
	}
	paramMap["batch"] = "false"
	paramMap["output"] = "xml"
	paramMap["key"] = aMapWebServiceKey
	urlParam := ""
	var keys []string
	for k := range paramMap {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	sep := ""
	for _, k := range keys {
		urlParam += sep + k + "=" + paramMap[k]
		sep = "&"
	}

	// 接口签名。参考 http://lbs.amap.com/faq/account/key/72
	aMapWebServicePrivate := conf.AMapKeys.WebServicePrivate
	if len(aMapWebServicePrivate) != 0 {
		urlParamPre := urlParam + aMapWebServicePrivate
		d := md5.New()
		d.Write([]byte(urlParamPre))
		urlParam += "sig=" + fmt.Sprintf("%x", d.Sum(nil))
	}

	// 完成 URL

	weatherURL += "?" + urlParam

	log4go.Finest("Weather URL: %v" + weatherURL)

	// HTTP 请求

	resp, err := http.Get(weatherURL)
	if err != nil {
		log4go.Error(err)
		return ErrFailure, nil
	}

	defer resp.Body.Close()

	// 解析 HTTP 回响

	var weatherResp amapWeatherResp
	err = xml.NewDecoder(resp.Body).Decode(&weatherResp)
	if err != nil {
		log4go.Warn("Weather failure: the http response seems not xml: %v", err)
		return ErrFailure, nil
	}

	log4go.Finest("Weather response: %v", weatherResp)

	if weatherResp.Status == "0" {
		log4go.Warn("Weather failure: the http response false: %v", weatherResp)
		return ErrFailure, nil
	}
	if weatherResp.InfoCode != "10000" {
		log4go.Warn("Weather failure: the http response false: %v", weatherResp)
		return ErrFailure, nil
	}
	if extensions {
		if len(weatherResp.ForecastList.Forecasts) == 0 || len(weatherResp.ForecastList.Forecasts[0].CastList.Casts) == 0 {
			log4go.Warn("Weather failure: the http response has no forecast or cast: %v", weatherResp)
			return ErrFailure, nil
		}
	} else {
		if len(weatherResp.LiveList.Lives) == 0 {
			log4go.Warn("Weather failure: the http response has no live: %v", weatherResp)
			return ErrFailure, nil
		}
	}

	return ErrSuccess, &weatherResp
}

//  API说明：https://lbs.amap.com/api/webservice/guide/api/convert
//  坐标转换回响
type amapConvertCoordinateResp struct {
	Status    string `xml:"status"`              // 返回结果状态值: 返回值为 0 或 1，0 表示请求失败；1 表示请求成功。
	Info      string `xml:"info,omitempty"`      // 返回状态说明: 当 status 为 0 时，info 会返回具体错误原因，否则返回“ok”
	InfoCode  string `xml:"infocode,omitempty"`  // 返回状态说明，10000代表正确
	Locations string `xml:"locations,omitempty"` // 成功时返回转换后的高德坐标，形如"116.487585177952,39.991754014757;116.487585177952,39.991653917101"
}

func amapConvertCoordinate(ics []*LatLon) (errCode ErrCode, ocs []*LatLon) {
	const maxCoorCount = 40
	if ics == nil {
		log4go.Warn("input parameter ics is nil")
		return ErrInvalidParam, nil
	} else if len(ics) == 0 {
		log4go.Warn("length of input parameter len(ics) is 0")
		return ErrInvalidParam, nil
	} else if len(ics) > maxCoorCount {
		log4go.Warn("length of input parameter(more than 40) is ", len(ics))
		return ErrInvalidParam, nil
	}
	aMapWebServiceKey := conf.AMapKeys.WebService
	if len(aMapWebServiceKey) == 0 {
		log4go.Trace("AMapWebServiceKey is empty")
		return ErrFailure, nil
	}

	//http://restapi.amap.com/v3/assistant/coordinate/convert?locations=116.481499,39.990475&coordsys=gps&output=xml&key=<用户的key>
	coordConvURL := "http://restapi.amap.com/v3/assistant/coordinate/convert"

	latLons := ""
	sep := ""
	for _, c := range ics {
		//urlParam += sep + fmt.Sprintf("%.6f", c.Lon) + "," + fmt.Sprintf("%.6f", c.Lat)
		latLons += sep + fmt.Sprintf("%v", c.Lon) + "," + fmt.Sprintf("%v", c.Lat)
		sep = "|"
	}

	paramMap := map[string]string{}
	paramMap["locations"] = latLons
	paramMap["coordsys"] = "gps"
	paramMap["output"] = "xml"
	paramMap["key"] = aMapWebServiceKey
	urlParam := ""
	var keys []string
	for k := range paramMap {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	sep = ""
	for _, k := range keys {
		urlParam += sep + k + "=" + paramMap[k]
		sep = "&"
	}

	// 接口签名。参考 http://lbs.amap.com/faq/account/key/72
	aMapWebServicePrivate := conf.AMapKeys.WebServicePrivate
	if len(aMapWebServicePrivate) != 0 {
		urlParamPre := urlParam + aMapWebServicePrivate
		d := md5.New()
		d.Write([]byte(urlParamPre))
		urlParam += "sig=" + fmt.Sprintf("%x", d.Sum(nil))
	}

	// 完成 URL
	coordConvURL += "?" + urlParam
	log4go.Finest("ConvertCoordinate URL: %v", coordConvURL)

	// HTTP 请求
	resp, err := http.Get(coordConvURL)
	if err != nil {
		log4go.Error(err)
		return ErrFailure, nil
	}

	defer resp.Body.Close()

	// 解析 HTTP 回响
	var convertCoordinateResp amapConvertCoordinateResp
	err = xml.NewDecoder(resp.Body).Decode(&convertCoordinateResp)
	if err != nil {
		log4go.Warn("ConvertCoordinate failure: the http response seems not xml: %v", err)
		return ErrFailure, nil
	}

	log4go.Finest("ConvertCoordinate response: %v", convertCoordinateResp)

	if convertCoordinateResp.Status == "0" {
		log4go.Warn("ConvertCoordinate failure: the http response false: %v", convertCoordinateResp)
		return ErrFailure, nil
	}
	if convertCoordinateResp.InfoCode != "10000" {
		log4go.Warn("ConvertCoordinate failure: the http response false: %v", convertCoordinateResp)
		return ErrFailure, nil
	}
	if len(convertCoordinateResp.Locations) == 0 {
		log4go.Warn("ConvertCoordinate failure: len(convertCoordinateResp.Locations) == 0")
		return ErrFailure, nil
	}

	locations := strings.Split(convertCoordinateResp.Locations, ";")
	for _, location := range locations {
		lonLat := strings.Split(location, ",")
		if len(lonLat) != 2 {
			log4go.Warn("ConvertCoordinate failure: location field of http response seems not latitude and longitude %v", lonLat)
			return ErrFailure, nil
		}
		lon, err := strconv.ParseFloat(lonLat[0], 64)
		if err != nil {
			log4go.Warn("ConvertCoordinate failure: location field of http response seems not longitude %v", lonLat[0])
			return ErrFailure, nil
		}
		lat, err := strconv.ParseFloat(lonLat[1], 64)
		if err != nil {
			log4go.Warn("ConvertCoordinate failure: location field of http response seems not latitude %v", lonLat[1])
			return ErrFailure, nil
		}
		ocs = append(ocs, &LatLon{Lat: lat, Lon: lon})
	}

	return ErrSuccess, ocs
}

type amap struct {
}

func (a *amap) Lbs(netConnType NetConnectionType, cellRadioType CellRadioType, imei, imsi string, cells []*Cell, wifis []*Wifi) (errCode ErrCode, position *Position) {
	stations := &Station{
		NetConnType:   netConnType,
		CellRadioType: cellRadioType,
		Imsi:          imsi,
		Cells:         cells,
		Wifis:         wifis,
	}
	errCode, locateResp := amapLbsLocate(imei, stations)
	if errCode != ErrSuccess {
		return errCode, nil
	}

	position = &Position{}
	if locateResp.Result.Country == "中国" {
		position.Gcj02.Lat = locateResp.Result.Latitude
		position.Gcj02.Lon = locateResp.Result.Longitude
		position.Wgs84.Lat, position.Wgs84.Lon = GPSGCJ02ToWGS84(position.Gcj02.Lat, position.Gcj02.Lon)
	} else {
		position.Wgs84.Lat = locateResp.Result.Latitude
		position.Wgs84.Lon = locateResp.Result.Longitude
		position.Gcj02.Lat, position.Gcj02.Lon = position.Wgs84.Lat, position.Wgs84.Lon
	}

	position.Accuracy = locateResp.Result.Accuracy

	if len(stations.Cells) > 0 && len(stations.Wifis) > 0 {
		if netConnType == NetConnTypeWifi {
			position.LocationType = WIFI
		} else {
			position.LocationType = CELL
		}
	} else if len(stations.Cells) > 0 {
		position.LocationType = CELL
	} else {
		position.LocationType = WIFI
	}

	// 填充 address
	position.Addr = &Address{}
	position.Addr.Country = locateResp.Result.Country
	position.Addr.Province = locateResp.Result.Province
	position.Addr.City = locateResp.Result.City
	position.Addr.CityCode = locateResp.Result.CityCode
	position.Addr.AdCode = locateResp.Result.AdCode
	position.Addr.Road = locateResp.Result.Road
	position.Addr.Street = locateResp.Result.Street
	position.Addr.POI = locateResp.Result.Poi
	position.Addr.Desc = locateResp.Result.Desc

	return
}

func (a *amap) LbIP(ip string) (errCode ErrCode, position *Position) {
	errCode, locateResp := amapLbIP(ip)
	if errCode != ErrSuccess {
		return errCode, nil
	}

	position = &Position{}

	// 填充 address
	position.Addr = &Address{}
	position.Addr.Province = locateResp.Province
	position.Addr.City = locateResp.City
	position.Addr.AdCode = locateResp.AdCode

	return
}

func (a *amap) Regeocode(position *Position, accuracy float64) (errCode ErrCode, addr *Address) {
	errCode, regeocodeResp := amapRegeocode(&position.Gcj02, accuracy)
	if errCode != ErrSuccess {
		return
	}

	addr = &Address{}
	addr.Country = regeocodeResp.Regeocode.AddressComponent.Country
	addr.Province = regeocodeResp.Regeocode.AddressComponent.Province
	addr.CityCode = regeocodeResp.Regeocode.AddressComponent.Citycode
	addr.City = regeocodeResp.Regeocode.AddressComponent.City
	if len(addr.City) == 0 && (addr.CityCode == "010" || addr.CityCode == "021" || addr.CityCode == "022" || addr.CityCode == "023") {
		addr.City = addr.Province
	}
	addr.District = regeocodeResp.Regeocode.AddressComponent.District
	addr.AdCode = regeocodeResp.Regeocode.AddressComponent.AdCode
	addr.Township = regeocodeResp.Regeocode.AddressComponent.Township
	addr.TownCode = regeocodeResp.Regeocode.AddressComponent.TownCode
	addr.Street = regeocodeResp.Regeocode.AddressComponent.StreetNumber.Street
	if len(regeocodeResp.Regeocode.RoadList.Roads) > 0 {
		addr.Road = regeocodeResp.Regeocode.RoadList.Roads[0].Name
	}
	if len(regeocodeResp.Regeocode.PoiList.Pois) > 0 {
		addr.POI = regeocodeResp.Regeocode.PoiList.Pois[0].Name
	}
	addr.Desc = regeocodeResp.Regeocode.FormattedAddr

	return
}
